#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include "keyboard.h"
#include "process.h"
#include "pcb.h"
#include "queue.h"

#define TIMER_INTERRUPT 11
#define SYSTEM_CALL 12

typedef struct comp_str
{
	int pc_reg;
	PCB current_process;

} CompStr;
typedef CompStr * Comp;

int interrupt;
pthread_mutex_t interrupt_lock;
//pthread_cond_t interrupt_cond;

PQueue *ready_queue;

int running;
pthread_mutex_t run_lock;
pthread_cond_t run_cond;

pthread_mutex_t step_lock;

Comp my_cpu;

Process process_init(int the_number_of_steps, int the_number_of_requests,
                     int *the_array_of_requests, int the_id, function_ptr the_func)
{
    Process temp = (Process) malloc(sizeof(ProcessStr));
    temp->no_steps = the_number_of_steps;
    temp->no_requests = the_number_of_requests;
    temp->requests = the_array_of_requests;
    temp->function = the_func;
    temp->identifier = the_id;
    return temp;
}

PCB pcb_init(int the_pid, int the_next_step, Process the_process, int the_sem) //the_sem should be -1 if it does not own one
{
    PCB temp = (PCB) malloc(sizeof(PCBStr));
    temp->pid = the_pid;
    temp->id = the_process;
    temp->next_step = the_next_step;
    temp->owns = the_sem;

    return temp;
}

void *pcb_run(void *the_pcb)
{
    PCB temp = (PCB)the_pcb;
    int current_step = temp->next_step;
    while(temp->next_step < temp->id->no_steps)
    {
        pthread_mutex_lock(&run_lock);
        while(running != temp->id->identifier)
        {
            printf("Thread %d waiting to run.\n", temp->pid);
            pthread_cond_wait(&run_cond, &run_lock);
        }
        pthread_mutex_lock(&step_lock);
        if (current_step != temp->next_step)
        {
            current_step = temp->next_step;
            printf("The current process running is %d.\n", temp->pid);
            printf("Current step of this process is %d.\n", current_step);
        }
        pthread_mutex_unlock(&step_lock);
        pthread_mutex_unlock(&run_lock);

    }

    printf("\nTest ended.\n");

    return NULL;
}

void *sched_func(int inter)
{
    pthread_mutex_lock(&run_lock);
    printf("Scheduler has been called because a ");
    if (inter == TIMER_INTERRUPT)
    {
        printf("Timer interrupt has been detected.\n");
        PCB next_process = (PCB)queue_remove(ready_queue);
        running = next_process->id->identifier;
        queue_add_priority(ready_queue, my_cpu->current_process, 1); //put the current process into the ready queue
        my_cpu->current_process = next_process;
    }
    pthread_mutex_unlock(&run_lock);

    interrupt = 20; //no interrupt
    pthread_cond_signal(&run_cond);
}
/*
* Temporary timer run function. Will be updated as I work on the timer.
*/
void *timer_func(void *the_end)
{
    int end = *(int *)the_end;
    int start = 0;
    while(start < end)
    {
        sleep(3);
        interrupt = TIMER_INTERRUPT;
        printf("Timer Interrupt generated by timer.\n");
        start++;
    }
}

//for testing keyboard process
void *key_listen_thread(void *arg)
{
    pthread_t key_thread;
    void * exit_status;

    pthread_create(&key_thread, NULL, keyboard_listen, NULL);

    pthread_join(key_thread, &exit_status);

    printf("\nTest ended.\n");

    return NULL;
}

void *PROD_funct()
{
    //try to get semaphore
    //while memorytotal - available count >0
        //generate random number and put in mem location, increase available count
        //printf random number generated
}

void *CONS_funct()
{
    //try to get semaphore
    //while(available count>0)
        //get random number from mem location and decrease available count (do-able with the mutexes, right?)
        //printf random number grabbed
}

void *CALC_funct()
{
    int a = rand() % 45000; //close to square root of max int
    int b = rand() % 45000;
    int c = a * b;
    printf(c);
}

void *IO_funct()
{
    //not sure if we need this
}

int main(int argc, char *argv[])
{
    int cycle_count = 0;
    int max_cycle = 10; //This is set by a command line argument.
	char *processes;
    if (argc > 0) {
		max_cycle = atoi(argv[0]);

	}
	if (argc > 1) {
		process_init = argv[1];
	}



    ready_queue = queue_new();



    pthread_mutex_init(&interrupt_lock, NULL);
    //pthread_cond_init(&interrupt_cond, NULL);

    pthread_mutex_init(&run_lock, NULL);
    pthread_cond_init(&run_cond, NULL);

    pthread_mutex_init(&step_lock, NULL);

    my_cpu = (Comp) malloc(sizeof(CompStr));
    int * test_array = (int *) malloc(8);

    //Test processes
    //Process third = process_init(10, 0, test_array, PROCESS_3);
    //PCB third_pcb = pcb_init(3, 0, third);
    //queue_add_priority(ready_queue, third_pcb, 1);
    //Process first = process_init(10, 0, test_array, PROCESS_1);
    //PCB first_pcb = pcb_init(1, 0, first);
    //queue_add_priority(ready_queue, first_pcb, 1);
    //Process second = process_init(10, 0, test_array, PROCESS_2);
    //PCB second_pcb = pcb_init(2, 0, second);
    //queue_add_priority(ready_queue, second_pcb, 1);

    running = 0;
    interrupt = 0;
    pthread_t first_key;
    pthread_t second_key;
    pthread_t third_key;

    pthread_create(&first_key, NULL, pcb_run, first_pcb);
    pthread_create(&second_key, NULL, pcb_run, second_pcb);
    pthread_create(&third_key, NULL, pcb_run, third_pcb);

    my_cpu->current_process = (PCB)queue_remove(ready_queue);
    pthread_t timer_key;
    pthread_create(&timer_key, NULL, timer_func, &max_cycle);
    sched_func(TIMER_INTERRUPT);
    while (cycle_count < max_cycle)
    {
        sleep(1);
        cycle_count++;
        printf("Current cpu cycle %d.\n", cycle_count);
        pthread_mutex_lock(&interrupt_lock);
        switch (interrupt)
        {
            case TIMER_INTERRUPT:
                printf("Timer Interrupt recieved by CPU.\n");
                sched_func(TIMER_INTERRUPT);
                break;
            default:
                break; //do nothing
        }
        pthread_mutex_unlock(&interrupt_lock);
        pthread_mutex_lock(&step_lock);
        my_cpu->current_process->next_step++;
        pthread_mutex_unlock(&step_lock);
    }

    pthread_mutex_destroy(&interrupt_lock);
    //pthread_cond_destroy(&interrupt_cond);
    pthread_mutex_destroy(&run_lock);
    pthread_cond_destroy(&run_cond);
    pthread_mutex_destroy(&step_lock);
    free(test_array);
    free(my_cpu->current_process);
    free(my_cpu);
    free(first);
    free(second);
    free(third);
    queue_free(ready_queue);

    return 0;
}
